﻿//----------------------------------------------------------------
//CodeArt (2007)
//
//Author: jianyi0115@163.com
//Created: 2007-12-5
//
//History:
//101019 add QueryScope by jianyi
//----------------------------------------------------------------
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Data;

using Microsoft.SharePoint;
 
namespace CodeArt.SharePoint.CamlQuery
{
    /// <summary>
    /// query for list
    /// query gateway
    /// </summary>
    public static class ListQuery 
    {
        public static Select Select(params IQueryField[] fields)
        {
            QueryContext context = new QueryContext();
            Select s = new Select(context, fields);
            return s;
        }
        public static Select Select(params string[] fields)
        {
            QueryContext context = new QueryContext();
            Select s = new Select(context, fields);
            return s;
        }

        public static Select Select()
        {
            QueryContext context = new QueryContext();
            Select s = new Select(context);
            return s;
        }
         
        /// <summary>
        /// 
        /// </summary>
        /// <param name="top">limit result count</param>
        /// <param name="fields">the fields to return</param>
        /// <returns></returns>
        public static Select Select( uint top , params IQueryField[] fields)
        {
            QueryContext context = new QueryContext();
            context.RowLimit = top;

            Select s = new Select(context, fields);
            return s;
        }
        public static Select SelectContentType(string ctName)
        {
            QueryContext context = new QueryContext();
            context.ContentType = ctName;
            Select s = new Select(context);
            return s;
        }
        public static Select SelectContentType(string ctName, uint top)
        {
            QueryContext context = new QueryContext();
            context.RowLimit = top;
            context.ContentType = ctName;
            Select s = new Select(context);
            return s;
        }

        /// <summary>
        /// the list
        /// </summary> 
        /// <param name="list"></param>
        /// <returns></returns>
        public static From From(SPList list)
        {
            Select select = ListQuery.Select();
            return select.From( list );
        }
        public static From From(SPView view)
        {
            Select select = ListQuery.Select();
            select.Context.View = view;
            return select.From(view.ParentList);
        }

        public static From From(SPList list, QueryScope scope)
        {
            Select select = ListQuery.Select();
            select.Context.Scope = scope;
            return select.From(list);
        }

        public static From From(SPView view, QueryScope scope)
        {
            Select select = ListQuery.Select();
            select.Context.Scope = scope;
            select.Context.View = view;
            return select.From(view.ParentList);
        }

        public static From From(SPList list,SPFolder folder, QueryScope scope)
        {
            Select select = ListQuery.Select();
            select.Context.Scope = scope;
            select.Context.Folder = folder;
            return select.From(list);
        }
        public static From From(SPView view, SPFolder folder, QueryScope scope)
        {
            Select select = ListQuery.Select();
            select.Context.Scope = scope;
            select.Context.Folder = folder;
            select.Context.View = view;
            return select.From(view.ParentList);
        }    
    }

    /// <summary>
    /// 
    /// </summary>
    class QueryContext
    {
        internal QueryContext() { }

        public  SPList List ;

        public SPView View;

        public SPFolder Folder;

        public SPList[] Lists;

        public string ContentType;

        public static QueryField ContentTypeField = new QueryField("ContentType", true);

        public IQueryField[] ViewFields;
        public string[] ViewFieldsNameOnly;

        public readonly List<Join> Joins = new List<Join>();

        public uint RowLimit;

        public ICamlExpression Query ;

        public ICamlExpression GetQueryExpression()
        {
            if (!String.IsNullOrEmpty(this.ContentType))
            {
                if(this.Query != null)
                    return (ContentTypeField == this.ContentType) && (CamlExpression)this.Query;
                else
                    return (ContentTypeField == this.ContentType);
            }
            else
            {
                return this.Query;
            }                
        }

        public IDictionary<IQueryField, bool> OrderByFields = new Dictionary<IQueryField, bool>();

        public IQueryField GroupByField;

        public QueryScope Scope = QueryScope.Default;

        static string[] SystemFields = new string[] { "Author", "Editor", "Created", "Modified", "FileRef" };

        public string[] GetQueryFields()
        {
            if(this.ViewFields!=null && this.ViewFields.Length>0)
            {
                return this.ViewFields.Select(x=>x.FieldName).ToArray();
            }
            else if (this.ViewFieldsNameOnly != null && this.ViewFieldsNameOnly.Length > 0)
            {
                return this.ViewFieldsNameOnly;
            }
            else if (!String.IsNullOrEmpty(this.ContentType))
            {
                var ct = List.ContentTypes[this.ContentType];
                if (ct == null)
                    throw new Exception(String.Format("can't find contentType [{0}] in the list", this.ContentType));
                var list = ct.Fields.OfType<SPField>()
                       .Select(x => x.InternalName).ToList();
                
                foreach (var f in SystemFields)
                {
                    if (list.Contains(f) == false)
                        list.Add(f);
                }

                return list.ToArray();
            }
            else
            {
                return null;
            }
        }
    }

    /// <summary>
    ///  base class of query sentence
    /// </summary>
    public abstract class QuerySentence
    {
        internal QuerySentence(QueryContext context)
        {
            Context = context;
        }

        internal readonly QueryContext Context ;

        internal string ToCamlString()
        {
            return CamlBuilder.View(this.Context);
        }

        public override string ToString()
        {
            return ToCamlString();
        }
    }

    /// <summary>
    /// sentence can return data.
    /// </summary>
    public abstract class ReturnableQuerySentence : QuerySentence
    {
        internal ReturnableQuerySentence(QueryContext c)
            : base(c)
        {
        }

        public SPQuery GetQuery()
        {
            SPQuery query = Context.View == null ? new SPQuery() : new SPQuery(Context.View);

            if (Context.RowLimit > 0)
                query.RowLimit = Context.RowLimit;

            var queryFields = Context.GetQueryFields();
            if (queryFields != null && queryFields.Length > 0)
            {
                query.ViewFields = CamlBuilder.ViewFields(queryFields);
            }

            string initWhere = string.Empty;
            string initOrder = string.Empty;

            if (Context.View != null)
            {
                CamlBuilder.ParserQuery("<Query>" + Context.View.Query + "</Query>", out initWhere, out initOrder);
            }

            string qry = string.Empty;

            var q = Context.GetQueryExpression();
            if (q != null)
            {
                string whereInner = CamlBuilder.WhereInner(Context.List, q);
               
                if (String.IsNullOrEmpty(initWhere))
                    qry = "<Where>" + whereInner + "</Where>";
                else
                    qry = "<Where><And>" + initWhere + whereInner + "</And></Where>";               
            }
            else if(!String.IsNullOrEmpty(initWhere))
            {
                qry = "<Where>" + initWhere + "</Where>";
            }

            if (Context.OrderByFields.Count != 0)
            {
                qry = qry + CamlBuilder.OrderBy(Context.List, Context.OrderByFields);
            }
            else if (!String.IsNullOrEmpty(initOrder))
            {
                qry = qry + "<OrderBy>" + initOrder + "</OrderBy>";
            }

            query.Query = qry;
            if (this.Context.Scope != QueryScope.Default)
            {
                query.ViewAttributes = "Scope=\"" + this.Context.Scope + "\"";
            }

            if (Context.Folder != null)
                query.Folder = Context.Folder;

            if (Context.Joins.Count > 0)	
            {	
                Join.ApplyJoin(query,this.Context.Joins);	
            }

            return query;
        }

        /// <summary>
        /// get Items
        /// </summary>
        /// <returns></returns>
        public SPListItemCollection GetItems()
        {
            SPQuery query = this.GetQuery();
            SPListItemCollection items = Context.List.GetItems(query);
            return items;
        }

        public SPListItem FirstOrDefault()
        {
            this.Context.RowLimit = 1;
            SPListItemCollection items = GetItems();
            if (items.Count > 0)
                return items[0];
            else
                return null;
        }
        

        /// <summary>
        /// get DataTable
        /// </summary>
        /// <returns></returns>
        public DataTable GetDataTable()
        {
            SPListItemCollection items = GetItems();

            if (items != null)
                return items.GetDataTable();
            else
                return null;
        }        
    }

    public enum QueryScope
    {
        /// <summary>
        /// non scope
        /// </summary>
        Default,

        /// <summary>
        /// query sub folder (without folder contentType)
        /// </summary>
        Recursive,

        /// <summary>
        /// query sub folder and query folder contentType
        /// </summary>
        RecursiveAll,

        FilesOnly
    }

    //http://blog.thekid.me.uk/archive/2007/02/27/spsitedataquery-samples-for-wss-v3.aspx
    /// <summary>
    /// Ñ¡Ôñ
    /// select
    /// </summary>
    public class Select : QuerySentence
    {
        internal Select(QueryContext context)
            : base(context)
        {
            //context.ViewFields = fields;
        }
        internal Select(QueryContext context , params IQueryField[] fields) 
            : base( context )
        {
            context.ViewFields = fields ;
        }
        internal Select(QueryContext context, params string[] fields)
            : base(context)
        {
            context.ViewFieldsNameOnly = fields;
        }
        
        public From From(SPList list)
        {
            From from = new From(this.Context, list);
            return from;
        }
        public From From(SPView view)
        {
            From from = new From(this.Context, view.ParentList);
            from.Context.View = view;
            return from;
        }

        public From From(SPList list, QueryScope scope)
        {
            this.Context.Scope = scope;
            From from = new From(this.Context, list);
            return from;
        }

        public From From(SPView view, QueryScope scope)
        {
            var from = From(view.ParentList,scope);
            from.Context.View = view;
            return from;
        }

        public From From(SPList list, SPFolder folder, QueryScope scope)
        {
            this.Context.Scope = scope;
            this.Context.Folder = folder;
            From from = new From(this.Context, list);
            return from;
        }
        public From From(SPView view, SPFolder folder, QueryScope scope)
        {
            var from = From(view.ParentList, folder,scope);
            from.Context.View = view;
            return from;
        }
        //public From Form( SPList[] lsts)
        //{
        //    From from = new From(this.Context, lsts);
        //    return from;
        //}

        //public From Form(SPWeb web,SiteQueryScope scope , SPBaseType b )
        //{
        //    From from = new From(this.Context, web);
        //    return from;
        //}

        //public From Form(SPWeb web, SiteQueryScope scope, int serverTemplate )
        //{
        //    From from = new From(this.Context, web);
        //    return from;
        //}
 
    }

    /// <summary>
    /// 
    /// </summary>
    public class From : ReturnableQuerySentence 
    {
        internal From(QueryContext context , SPList list ) 
            : base(context)
        {
            context.List = list;
        }

        internal From(QueryContext context, SPList[] lists)
            : base(context)
        {
            context.Lists = lists;
        }        

        internal From(QueryContext context,SPWeb web )
            : base(context)
        {
           // context.Lists = lists;
        }

        public Where Where(ICamlExpression q) //where T : class
        {
            Where where = new Where(this.Context, q);
            return where;
        }

        public Order OrderBy(IQueryField field)
        {
            return new Order(this.Context, field, true);
        }

        public Order OrderBy(IQueryField field, bool desc)
        {
            return new Order(this.Context, field, desc);
        }     
    }
    
    public class Where : ReturnableQuerySentence
    {
        internal Where( QueryContext context , ICamlExpression expr)
            : base(context)
        {
            context.Query = expr;
        }

        public Order OrderBy(IQueryField field)
        {
            return new Order(this.Context, field, true);
        }

        public Order OrderBy(IQueryField field, bool desc)
        {
            return new Order(this.Context, field, desc);
        }         

    }

    /// <summary>
    /// Provider orderby 
    /// </summary>
    public class Order : ReturnableQuerySentence
    {
        internal Order(QueryContext context, IQueryField field, bool desc)
            : base(context)
        {
            context.OrderByFields.Add(field, desc);
        }

        public Order OrderBy( IQueryField field , bool  desc )
        {
            this.Context.OrderByFields.Add(field, desc);
            return this;
        }         
    }

    /// <summary>
    /// Provider Group function
    /// </summary>
    public class Group : ReturnableQuerySentence
    {
        internal Group(QueryContext context, IQueryField field)
            : base(context)
        {
            context.GroupByField = field;
        }              
    }

//<GroupBy
//  Collapse = "TRUE" | "FALSE">
//  <FieldRef Name = "Field_Name"/>
//</GroupBy>


 
 
}

 